/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.test;


import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.SortedSet;

import dex.compiler.model.definition.Unit;
import dex.misc.IO;

import junit.framework.TestSuite;


/**
 * A test suite composed of template test cases.  A template test case
 * involves loading a template Dex source file, performing a simple
 * textual substitution on that template, parsing the resulting code
 * and performing some test on the parsed unit.
 * 
 * <p>One template file can therefore contain more than one test case.
 * The test cases are extracted from the comments in the template file.
 * Any comment that starts with a colon is considered a test case
 * description.  The description should consist of a textual substitution,
 * followed by <code>:=</code>, followed by the expected result.
 * For instance:
 * 
 * <pre>
 * unit example
 * 
 * \//:  true := FAIL
 * \//:  false := FAIL
 * \//:  0 := FAIL
 * \//:  1 := FAIL
 * \//:  1511412 := FAIL
 * 
 * void main() {
 *     return TEMPLATE
 * }
 * </pre>
 * 
 * <p>The above template source file would become five distinct test
 * cases, described by the five comments.  Each one would have the special 
 * token <code>TEMPLATE</code> replaced with the string between <code>\//</code> 
 * and <code>:=</code>.  The altered template would then be parsed, and the
 * unit would be passed to {@link #doTest(Unit,String)} with the string
 * value after <code>:=</code>.
 * 
 * <p>Implementations can decide if the given unit generates the expected
 * result.  The result strings are arbitrary; specific subclasses can
 * impose whatever restrictions they need on the expected result strings,
 * or ignore them altogether.
 * 
 * <p>This framework allows similar tests to be described in the same file,
 * so that many tests can be written quickly.
 * 
 */
public abstract class TemplateSuite extends TestSuite {


	/**
	 * The program to test.
	 */
	final TesterProgram program;


	/**
	 * Constructs a new <code>TemplateSuite</code> out of files in the given
	 * directory.
	 * 
	 * @param dir   the directory containing template source files
	 * @param expected   the list of expected file names
	 * @throws IOException  if the template files cannot be read
	 */
	public TemplateSuite(File dir, SortedSet<String> expected) throws IOException {
		super(dir.getName());
		this.program = new TesterProgram(new File("test/dex/common"));
		addTest(new CoverageTester(dir, expected));
		File[] files = dir.listFiles();
		for (File f: files) {
			if (isTest(f.getName())) {
				add(f);
			}
		}
	}

	
	private void add(File f) throws IOException {
		TestSuite suite = new TestSuite(f.getName());
		String template = IO.read(new FileReader(f));
		
		BufferedReader br = new BufferedReader(new StringReader(template));
		for (String s = br.readLine(); s != null; s = br.readLine()) {
			s = s.trim();
			if (s.startsWith("//!")) {
				int p = s.indexOf(" := ");
				String replacement = s.substring(3,p).trim();
				String expected = s.substring(p + 4).trim();
				suite.addTest(new TemplateCase(this, f.getName(), template, replacement, expected));
			}
		}
		
		if (suite.testCount() == 0) {
			this.addTest(new TemplateCase(this, f.getName(), template, f.getName(), f.getName()));
		} else {
			this.addTest(suite);
		}
	}

	
	/**
	 * Returns the program being tested.
	 * 
	 * @return  the program being tested
	 */
	public TesterProgram getProgram() {
		return program;
	}


	/**
	 * Runs a test.
	 * 
	 * @param unit       the parsed unit generated from a template source file
	 * @param expected   the expected result of the test
	 */
	protected abstract void doTest(Unit unit, String expected) throws Exception;


	/**
	 * Converts a filename into a test name.  This is used for testing
	 * coverage.  
	 * 
	 * @param fileName  the file name to translate
	 * @return   the static field name for that file name
	 */
	public static String getTestName(String fileName) {
		int p = fileName.lastIndexOf('_');
		return fileName.substring(0,p);
	}


	/**
	 * Returns true if the given filename represents a Dex template source
	 * template file.  The filename must consist of uppercase
	 * letters and the underscore, or this method returns false.
	 * 
	 * @param fileName  the filename to test
	 * @return   true if the filename represents a test case
	 */
	public static boolean isTest(String fileName) {
		int p = fileName.lastIndexOf('_');
		if (p < 0) {
			return false;
		}
		fileName = getTestName(fileName);
		for (int i = 0; i < fileName.length(); i++) {
			char ch = fileName.charAt(i);
			if ((ch != '_') && !Character.isUpperCase(ch)) {
				System.out.println("Illegal char: " + ch);
				return false;
			}
		}
		return true;
	}

}
